package net.barashev.seclub.io;

import java.util.Arrays;

import net.barashev.seclub.io.marshaller.IntegerMarshaller;

public abstract class VarLengthRecordBlock<T, M extends DataMarshaller<T>> {
    private final byte[] bytes;
    private final M marshaller;
    private final int blockSize;
    private static final DataMarshaller<Integer> INTEGER_MARSHALLER = new IntegerMarshaller();

    public VarLengthRecordBlock(byte[] bytes, int blockSize, M marshaller) {
        this.bytes = bytes;
        this.blockSize = blockSize;
        this.marshaller = marshaller;
    }

    public int getRecordCount() {
        return INTEGER_MARSHALLER.parseFrom(this.bytes, getBlockStartOffset());
    }

    private int getRecordOffset(int idx) {
        assert idx >=0 && idx < getRecordCount();
        return INTEGER_MARSHALLER.parseFrom(
                this.bytes, getBlockStartOffset() + (idx+1)*4);
    }

    public T get(int idx) {
        int recordOffset = getRecordOffset(idx);
        return this.marshaller.parseFrom(this.bytes, recordOffset);
    }

    public boolean set(int idx, T value) {
        assert idx >=0 && idx < getRecordCount();
        byte[] bytes = this.marshaller.write(value);
        int recordOffset = getRecordOffset(idx);
        int prevRecordOffset = idx == 0 ?
                getBlockStartOffset() + this.blockSize : getRecordOffset(idx-1);
        int availableSpace = prevRecordOffset - recordOffset;
        if (availableSpace < bytes.length) {
            if (shiftRecords(idx, bytes.length - availableSpace)) {
                recordOffset = getRecordOffset(idx);
            }
            else {
                return false;
            }
        }
        System.arraycopy(bytes, 0, this.bytes, recordOffset, bytes.length);
        return true;
    }

    private boolean shiftRecords(int idx, int shift) {
        int recordCount = getRecordCount();
        int lastRecordOffset = getRecordOffset(recordCount - 1);
        if (lastRecordOffset - shift < getBlockStartOffset() + (recordCount + 1)*4) {
            return false;
        }
        int prevRecordOffset = idx == 0 ?
                getBlockStartOffset() + this.blockSize : getRecordOffset(idx-1);
        System.arraycopy(this.bytes, lastRecordOffset, this.bytes, lastRecordOffset - shift,
                prevRecordOffset - lastRecordOffset);
        for (int i = idx; i < recordCount; i++) {
            int curRecordOffset = getRecordOffset(i);
            byte[] newRecordOffset = INTEGER_MARSHALLER.write(curRecordOffset - shift);
            System.arraycopy(newRecordOffset, 0, this.bytes, getBlockStartOffset() + (i+1)*4, 4);
        }
        return true;
    }

    protected abstract int getBlockStartOffset();
}
